์บ์‹ฑ ์ „๋žต

  • ์บ์‹œ ์ ์ค‘ : ์บ์‹œ์— ์ ‘๊ทผํ–ˆ์„ ๋•Œ ์ฐพ๊ณ  ์žˆ๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ
  • ์บ์‹œ ๋ˆ„๋ฝ : ์บ์‹œ์— ์ ‘๊ทผํ–ˆ์„ ๋•Œ ์ฐพ๊ณ  ์žˆ๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ
  • ์‚ญ์ œ ์ •์ฑ… : ์บ์‹œ์— ๊ณต๊ฐ„์ด ๋ถ€์กฑํ•  ๋•Œ ๊ณต๊ฐ„์„ ํ™•๋ณดํ•˜๋Š” ๋ฐฉ๋ฒ•

Cache-Aside

  • Lazy Loading, ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•  ๋•Œ ํ•ญ์ƒ ์บ์‹œ๋ฅผ ๋จผ์ € ํ™•์ธํ•˜๋Š” ์ „๋žต
  • ํ•„์š” ๋ฐ์ดํ„ฐ๋งŒ ์บ์‹œ์— ๋ณด๊ด€
  • ์ตœ์ดˆ ์กฐํšŒ์‹œ ์บ์‹œ ํ™•์ธ์„ ์œ„ํ•ด ์‹œ๊ฐ„์ด ์ƒ๋Œ€์ ์œผ๋กœ ์˜ค๋ž˜ ๊ฑธ๋ฆผ

Write-Through

  • ๋ฐ์ดํ„ฐ ์ž‘์„ฑ ์‹œ ํ•ญ์ƒ ์บ์‹œ์— ์ž‘์„ฑํ•˜๊ณ  ์›๋ณธ์—๋„ ์ž‘์„ฑํ•˜๋Š” ์ „๋žต
  • ์บ์‹œ์˜ ๋ฐ์ดํ„ฐ ์ƒํƒœ๊ฐ€ ํ•ญ์ƒ ์ตœ์‹ ์ž„์„ ๋ณด์žฅ
  • ์ž์ฃผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ๋„ ์บ์‹œ์— ์ค‘๋ณตํ•ด์„œ ์ €์žฅ โ†’ ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆผ

Write-Behind

  • ์บ์‹œ์—๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์ผ์ • ์ฃผ๊ธฐ๋กœ ์›๋ณธ์„ ๊ฐฑ์‹ ํ•˜๋Š” ๋ฐฉ์‹
  • ์บ์‹œ ๋ฐ์ดํ„ฐ๊ฐ€ ์›๋ณธ์— ์ ์šฉํ•˜๊ธฐ ์ „ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋ฐ์ดํ„ฐ ์†Œ์‹ค๋  ์œ„ํ—˜์„ฑ O

Spring Cache

CacheConfig

@Configuration
@EnableCaching
public class CacheConfig {
 
    @Bean
    // CacheManager๋กœ ์ง„ํ–‰ํ•ด๋„ ์ •์ƒ ๋™์ž‘
    public RedisCacheManager cacheManager(
            RedisConnectionFactory redisConnectionFactory
    ) {
        // ์„ค์ • ๊ตฌ์„ฑ์„ ๋จผ์ € ์ง„ํ–‰ํ•œ๋‹ค.
        // Redis๋ฅผ ์ด์šฉํ•ด์„œ Spring Cache๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ
        // Redis ๊ด€๋ จ ์„ค์ •์„ ๋ชจ์•„๋‘๋Š” ํด๋ž˜์Šค
        RedisCacheConfiguration configuration = RedisCacheConfiguration
                .defaultCacheConfig()
                // null์„ ์บ์‹ฑ ํ• ๊ฒƒ์ธ์ง€
                .disableCachingNullValues()
                // ๊ธฐ๋ณธ ์บ์‹œ ์œ ์ง€ ์‹œ๊ฐ„ (Time To Live)
                .entryTtl(Duration.ofSeconds(10))
                // ์บ์‹œ๋ฅผ ๊ตฌ๋ถ„ํ•˜๋Š” ์ ‘๋‘์‚ฌ ์„ค์ •
                .computePrefixWith(CacheKeyPrefix.simple())
                // ์บ์‹œ์— ์ €์žฅํ•  ๊ฐ’์„ ์–ด๋–ป๊ฒŒ ์ง๋ ฌํ™” / ์—ญ์ง๋ ฌํ™” ํ• ๊ฒƒ์ธ์ง€
                .serializeValuesWith(
                        SerializationPair.fromSerializer(RedisSerializer.java())
                );
                
        return RedisCacheManager
                .builder(redisConnectionFactory)
                .cacheDefaults(configuration)
                .build();
    }
}

@EnableCaching

// cacheNames: ๋ฉ”์„œ๋“œ๋กœ ์ธํ•ด์„œ ๋งŒ๋“ค์–ด์งˆ ์บ์‹œ๋ฅผ ์ง€์นญํ•˜๋Š” ์ด๋ฆ„
// key: ์บ์‹œ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด ํ™œ์šฉํ•  ๊ฐ’
@Cacheable(cacheNames = "itemCache", key = "args[0]")
public ItemDto readOne(Long id) {
    log.info("Read One: {}", id);
    return repository.findById(id)
            .map(ItemDto::fromEntity)
            .orElseThrow(() 
                    -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}

@CachePut

@CachePut(cacheNames = "itemCache", key = "#result.id")
public ItemDto create(ItemDto dto) {
    return ItemDto.fromEntity(itemRepository.save(Item.builder()
            .name(dto.getName())
            .description(dto.getDescription())
            .price(dto.getPrice())
            .stock(dto.getStock())
            .build()
    ));
}

@CacheEvict

@CachePut(cacheNames = "itemCache", key = "args[0]")
@CacheEvict(cacheNames = "itemAllCache", allEntries = true)
public ItemDto update(Long id, ItemDto dto) {
    Item item = itemRepository.findById(id)
                    .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
    item.setName(dto.getName());
    item.setDescription(dto.getDescription());
    item.setPrice(dto.getPrice());
    item.setStock(dto.getStock());
    return ItemDto.fromEntity(itemRepository.save(item));
}